﻿// The MIT License (MIT)
// Copyright (c) 2014 Grégory Ghez
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

namespace JenkinsController.ViewModel
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    using System.Windows.Threading;
    using Common.Logging;
    using Framework;
    using GalaSoft.MvvmLight;
    using GalaSoft.MvvmLight.Command;
    using GalaSoft.MvvmLight.Messaging;
    using Jenkins.Domain;
    using Microsoft.Practices.ServiceLocation;
    using Model;
    using Properties;
    using View;
    using Application = System.Windows.Application;

    internal class MainViewModel : ViewModelBase, IDisposable
    {
        private static readonly ILog Logger = LogManager.GetCurrentClassLogger();
        private readonly IDataService dataService;
        private readonly ServerManagerViewModel serverManager;
        private readonly DispatcherTimer timer;
        private bool isDisposed;
        private bool isLoading;
        private bool isVisible;
        private string lastErrorMessage;
        private DateTime? lastUpdate;
        private NotifyIcon notifier;
        private JenkinsJob selectedJob;
        private JenkinsJobStatus selectedJobStatus;
        private JenkinsServer selectedServer;
        private JenkinsUser selectedUser;
        private JenkinsView selectedView;
        private IEnumerable<JenkinsServer> servers;

        public MainViewModel(IMessenger messenger, IDataService dataService, ServerManagerViewModel serverManager)
            : base(messenger)
        {
            this.dataService = dataService;
            this.dataService.StartLoading += (s, e) =>
            {
                this.IsLoading = true;
                this.LastErrorMessage = null;
            };
            this.dataService.EndLoading += (s, e) =>
            {
                this.IsLoading = false;
                this.LastUpdate = DateTime.Now;
            };
            this.serverManager = serverManager;

            this.PrepareNotifier();

            messenger.Register<ConfigClosingMessage>(this, this.OnConfigClosingMessageReceived);
            messenger.Register<ServerListUpdatedMessage>(this, this.OnServerListUpdatedMessageReceived);
            messenger.Register<HelpMessage>(this, msg => this.ShowHelp());
            messenger.Register<ErrorMessage>(this, this.OnErrorMessageReceived);

            this.LoadSettings();

            this.timer = new DispatcherTimer(TimeSpan.FromSeconds(Settings.Default.PollingInterval), DispatcherPriority.Background, this.OnTimerTick, Application.Current.Dispatcher);
            this.timer.Start();

            this.IsVisible = true;
        }

        public IEnumerable<JenkinsJob> FilteredJobs
        {
            get
            {
                return this.SelectedView == null || this.SelectedView.Jobs == null
                    ? null
                    : this.SelectedView.Jobs.Where(j => this.SelectedJobStatus == null || Equals(j.Status, this.SelectedJobStatus));
            }
        }

        public JenkinsJobStatus SelectedJobStatus
        {
            get { return this.selectedJobStatus; }
            set
            {
                this.selectedJobStatus = value;
                this.RaisePropertyChanged(() => this.SelectedJobStatus);
                this.RaisePropertyChanged(() => this.FilteredJobs);
            }
        }

        public RelayCommand<JenkinsTestReport> TestReportCommand
        {
            get { return new RelayCommand<JenkinsTestReport>(r => MessageBox.Show("Not implemented yet."), r => r != null); }
        }

        public string LastErrorMessage
        {
            get { return this.lastErrorMessage; }
            set
            {
                this.lastErrorMessage = value;
                this.RaisePropertyChanged(() => this.LastErrorMessage);
            }
        }

        public DateTime? LastUpdate
        {
            get { return this.lastUpdate; }
            set
            {
                this.lastUpdate = value;
                this.RaisePropertyChanged(() => this.LastUpdate);
            }
        }

        public JenkinsServer SelectedServer
        {
            get { return this.selectedServer; }
            set
            {
                this.selectedServer = value;
                if (this.selectedServer != null)
                {
                    this.dataService
                        .UpdateAsync(this.selectedServer)
                        .ContinueWith(ant =>
                        {
                            if (this.SelectedServer.Node != null && this.SelectedServer.Node.Views != null)
                            {
                                this.SelectedView = this.SelectedServer.Node.Views.FirstOrDefault(
                                    v => string.Equals(v.Url, this.SelectedServer.Node.PrimaryView.Url, StringComparison.OrdinalIgnoreCase));
                            }
                            else
                            {
                                this.SelectedView = null;
                            }
                        });
                }
                this.RaisePropertyChanged(() => this.SelectedServer);
            }
        }

        public JenkinsUser SelectedUser
        {
            get { return this.selectedUser; }
            set
            {
                this.selectedUser = value;
                this.ShowUserInJenkinsCommand.RaiseCanExecuteChanged();
                this.RaisePropertyChanged(() => this.SelectedUser);
            }
        }

        public RelayCommand HelpCommand
        {
            get { return new RelayCommand(this.ShowHelp); }
        }

        public RelayCommand<JenkinsBuild> LogsCommand
        {
            get { return new RelayCommand<JenkinsBuild>(b => this.MessengerInstance.Send(new LogsMessage { Build = b }), b => b != null); }
        }

        public RelayCommand AboutCommand
        {
            get { return new RelayCommand(() => this.MessengerInstance.Send(new WindowMessage<AboutView>())); }
        }

        public bool IsLoading
        {
            get { return this.isLoading; }
            set
            {
                this.isLoading = value;
                this.RaisePropertyChanged(() => this.IsLoading);
            }
        }

        public RelayCommand<JenkinsArtifact> DownloadArtifactCommand
        {
            get { return new RelayCommand<JenkinsArtifact>(this.DownloadArtifact); }
        }

        public IEnumerable<JenkinsServer> Servers
        {
            get { return this.servers; }
            set
            {
                this.servers = value;
                this.RaisePropertyChanged(() => this.Servers);
            }
        }

        public RelayCommand ConfigCommand
        {
            get { return new RelayCommand(() => this.MessengerInstance.Send(new WindowMessage<MainView>())); }
        }

        public RelayCommand QuitCommand
        {
            get { return new RelayCommand(() => Environment.Exit(0)); }
        }

        public RelayCommand<JenkinsJob> RunJobCommand
        {
            get { return new RelayCommand<JenkinsJob>(j => this.dataService.RunJobAsync(j), j => j != null && j.Status != null && !j.Status.IsRunning); }
        }

        public RelayCommand<JenkinsJob> StopJobCommand
        {
            get { return new RelayCommand<JenkinsJob>(j => this.dataService.StopJobAsync(j), j => j != null && j.Status != null && j.Status.IsRunning); }
        }

        public bool IsVisible
        {
            get { return this.isVisible; }
            set
            {
                this.isVisible = value;
                this.RaisePropertyChanged(() => this.IsVisible);
            }
        }

        public JenkinsJob SelectedJob
        {
            get { return this.selectedJob; }
            set
            {
                this.selectedJob = value;
                this.RunJobCommand.RaiseCanExecuteChanged();
                this.StopJobCommand.RaiseCanExecuteChanged();
                this.JobCommand.RaiseCanExecuteChanged();
                this.ChangeSetCommand.RaiseCanExecuteChanged();
                this.LogsCommand.RaiseCanExecuteChanged();
                this.RaisePropertyChanged(() => this.SelectedJob);
            }
        }

        public JenkinsView SelectedView
        {
            get { return this.selectedView; }
            set
            {
                this.selectedView = value;
                this.UpdateSelectedView();
                this.RaisePropertyChanged(() => this.SelectedView);
            }
        }

        public RelayCommand<JenkinsUser> ShowUserInJenkinsCommand
        {
            get
            {
                return new RelayCommand<JenkinsUser>(u =>
                {
                    try
                    {
                        Process.Start(u.Details.Url);
                    }
                    catch (Win32Exception winEx)
                    {
                        Logger.Error(CultureInfo.InvariantCulture, m => m("Cannot display user web page for url ({0}).", u.Details.Url), winEx);
                        this.MessengerInstance.Send(new ErrorMessage(winEx));
                    }
                }, u => u != null && u.Details != null);
            }
        }

        public RelayCommand<JenkinsJob> JobCommand
        {
            get
            {
                return new RelayCommand<JenkinsJob>(j => this.MessengerInstance.Send(new JobMessage { Job = j }), j => j != null);
            }
        }

        public RelayCommand ServerManagerCommand
        {
            get
            {
                return new RelayCommand(() =>
                {
                    var view = ServiceLocator.Current.GetInstance<ServerManagerView>();
                    view.ShowDialog();
                });
            }
        }

        public RelayCommand<JenkinsBuild> ChangeSetCommand
        {
            get { return new RelayCommand<JenkinsBuild>(b => this.MessengerInstance.Send(new ChangeSetMessage { Build = b }), b => b != null); }
        }

        public void Dispose()
        {
            if (!this.isDisposed)
            {
                this.Dispose(true);
                GC.SuppressFinalize(this);

                this.isDisposed = true;
            }
        }

        private async Task UpdateSelectedView()
        {
            if (this.SelectedView != null)
            {
                this.RaisePropertyChanged(() => this.FilteredJobs);
                await this.dataService.UpdateAsync(this.SelectedView);
                this.RaisePropertyChanged(() => this.FilteredJobs);

                foreach (var job in SelectedView.Jobs)
                {
                    await this.dataService.UpdateAsync(job);
                }

                this.RaisePropertyChanged(() => this.FilteredJobs);
            }
        }

        private void OnErrorMessageReceived(ErrorMessage msg)
        {
            this.LastErrorMessage = msg.Message;
        }

        ~MainViewModel()
        {
            this.Dispose(false);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.notifier != null)
                {
                    this.notifier.Dispose();
                }

                if (this.timer != null)
                {
                    this.timer.Stop();
                }

                this.SaveSettings();
            }
        }

        private void ShowHelp()
        {
            try
            {
                Process.Start("https://jenkinscontroller.codeplex.com/documentation");
            }
            catch (Win32Exception winEx)
            {
                this.MessengerInstance.Send(new ErrorMessage(winEx));
            }
        }

        private void OnServerListUpdatedMessageReceived(ServerListUpdatedMessage msg)
        {
            if (!msg.Servers.Contains(this.SelectedServer))
            {
                this.SelectedServer = msg.Servers.FirstOrDefault();
            }

            this.Servers = msg.Servers;

            this.SaveSettings();
        }

        private async void DownloadArtifact(JenkinsArtifact a)
        {
            string downloadFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Downloads");

            string artifactUrl = this.SelectedJob.LastBuild.Url + "artifact/" + a.RelativePath;
            var sfd = new SaveFileDialog
                      {
                          Title = "Save artifact as...",
                          FileName = a.FileName,
                          InitialDirectory = downloadFolderPath
                      };
            if (sfd.ShowDialog() == DialogResult.OK)
            {
                using (var wc = new WebClient())
                {
                    await wc.DownloadFileTaskAsync(new Uri(artifactUrl), sfd.FileName).ContinueWith(ant =>
                    {
                        try
                        {
                            Process.Start("explorer", Path.GetDirectoryName(sfd.FileName));
                        }
                        catch (Win32Exception winEx)
                        {
                            Logger.Error(CultureInfo.InvariantCulture, m => m("Cannot open folder ({0}).", Path.GetDirectoryName(sfd.FileName)), winEx);
                            this.MessengerInstance.Send(new ErrorMessage(winEx));
                        }
                        catch (FileNotFoundException fileEx)
                        {
                            Logger.Error(CultureInfo.InvariantCulture, m => m("Folder not found ({0}).", Path.GetDirectoryName(sfd.FileName)), fileEx);
                            this.MessengerInstance.Send(new ErrorMessage(fileEx));
                        }
                    });
                }
            }
        }

        private async void OnTimerTick(object sender, EventArgs e)
        {
            await this.UpdateSelectedView();
        }

        private void PrepareNotifier()
        {
            this.notifier = new NotifyIcon();
            this.notifier.Icon = Resources.Moustache;
            this.notifier.DoubleClick += (sender, args) =>
            {
                if (this.ConfigCommand.CanExecute(null))
                {
                    this.ConfigCommand.Execute(null);
                }
            };

            this.notifier.BalloonTipIcon = ToolTipIcon.Info;
            this.notifier.BalloonTipTitle = "Jenkins Job Manager";
            this.notifier.BalloonTipText = "Manage jobs from your configured Jenkins server here.";

            this.notifier.ShowBalloonTip(5000);

            this.notifier.ContextMenuStrip = new ContextMenuStrip();
            ToolStripItem configItem = this.notifier.ContextMenuStrip.Items.Add("Config");
            configItem.Image = Resources.Settings.ToBitmap();
            configItem.Click += (sender, args) =>
            {
                if (this.ConfigCommand.CanExecute(null))
                {
                    this.ConfigCommand.Execute(null);
                }
            };
            this.notifier.ContextMenuStrip.Items.Add(new ToolStripSeparator());
            this.notifier.ContextMenuStrip.Items.Add("Quit").Click += (sender, args) =>
            {
                if (this.QuitCommand.CanExecute(null))
                {
                    this.QuitCommand.Execute(null);
                }
            };

            this.notifier.Visible = true;
        }

        private void OnConfigClosingMessageReceived(ConfigClosingMessage msg)
        {
            this.SaveSettings();
            this.notifier.ShowBalloonTip(5000);
        }

        private async void LoadSettings()
        {
            if (Settings.Default.JenkinsUrls == null)
            {
                Settings.Default.JenkinsUrls = new StringCollection();
            }

            var servers = new List<JenkinsServer>();
            foreach (string url in Settings.Default.JenkinsUrls)
            {
                servers.Add(new JenkinsServer { Url = url, Node = new JenkinsNode() });
            }

            this.serverManager.Servers = new ObservableCollection<JenkinsServer>(servers);

            this.MessengerInstance.Send(new ServerListUpdatedMessage { Servers = servers });
        }

        private void SaveSettings()
        {
            if (Settings.Default.JenkinsUrls == null)
            {
                Settings.Default.JenkinsUrls = new StringCollection();
            }
            else
            {
                Settings.Default.JenkinsUrls.Clear();
            }

            foreach (JenkinsServer server in this.Servers)
            {
                Settings.Default.JenkinsUrls.Add(server.Url);
            }
            Settings.Default.Save();
        }
    }
}